/*
  Copyright 1996-2003
  Simon Whiteside

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

  $Id: Interpreter.java,v 1.62 2004/10/30 16:51:14 sdw Exp $
*/
package simkin;
import java.io.*;
import java.util.Vector;
import java.util.Hashtable;
//#ifndef no_reflect
import java.lang.reflect.Method;
import java.lang.reflect.Field;
//#endif

/**
 * This class represents the Simkin interpreter. The interpreter can load, parse and interpret Simkin code.
 * <p>The Interpreter maintains a list of global variables. This maps names to objects, which are available to all Simkin scripts.
 * <p>The interpreter itself is a global object with the name "Interpreter".
 * <p>The Interpreter implements the {@link simkin.Executable Executable} interface, supports the single field "tracing" which enables/disables tracing of method calls
 * <p>New from Simkin 1.22: callers are now responsible for creating Interpreter instances. There are no global interpreter variables in the class. Please see the document entitled "Creating Interpreters and Contexts" for more details.
 */
public class Interpreter extends ExecutableRoot {
    /**
     * this is the name of the interpreter global variable
     */
    static String s_Interpreter="Interpreter";
    /**
     * this is the name of the breakpoint method
     */
    static String s_breakpoint="breakpoint";
    /**
     * this is the name of the Interpreter's tracing variable, if true, the interpreter prints out tracing information
     */
    static String s_Tracing="tracing";
    /**
     * This table holds the global variables for this interpreter
     */
    Hashtable m_GlobalVars=new Hashtable();
    /**
     * if this variable is true, the interpreter outputs information about the execution of the code (method calls and return values)
     */
    boolean m_Tracing=false;
    /**
     * this variable sets tracing on for the functionality of the interpreter
     */
    static final boolean g_TraceExecution=false;
    /**
     * the null object
     */
    static public final Null g_null=new Null();    
    /** true object */
    static public final Boolean g_True=new Boolean(true);
    /** false object */
    static public final Boolean g_False=new Boolean(false);
    /** zero object */
    static public final Integer g_Zero=new Integer(0);
    /**
     * Object to receive trace message for this interpreter object
     */
    TraceCallback m_TraceCallback=null;
    /** Object to receive statement notifications */
    StatementStepper m_StatementStepper=null;
    /** Whether to enable Java reflective calls (by default enabled) */
    boolean m_ReflectionEnabled=true;

    /**
     * Default constructor - adds a global variable pointing to this interpreter
     */
    public Interpreter(){
	addGlobalVariable(s_Interpreter,this);
    }
    /** Returns the value of the reflection enabled flag. If this is true, the interpreter will use the
     * Java reflection API to look for public methods and fields
     * @return flag true or false
     */
    public boolean getReflectionEnabled(){
	return m_ReflectionEnabled;
    }
    /** Sets the value of the reflection enabled flag. If this is true, the interpreter will use the
     * Java reflection API to look for public methods and fields
     * @param flag true or false
     */
    public void setReflectionEnabled(boolean flag){
	m_ReflectionEnabled=flag;
    }
    /**
     * Used to set the "tracing" variable - for example in Simkin:
     * <p> Interpreter.tracing=true;
     */
    public void setValue(String field_name,String attrib,Object value,ExecutableContext ctxt) throws RuntimeException,FieldNotSupportedException{
	if (field_name.equals(s_Tracing)){
	    m_Tracing=boolValue(value);
	}else
	    super.setValue(field_name,attrib,value,ctxt);
    }
    /**
     * The following methods are supported directly by this class:
     * <p> 
     * <p>breakpoint - forces a breakpoint to be signalled to an attached debugger
     * @return true if the method is found
     */
    public Object method(String method_name,Object[] arguments, ExecutableContext ctxt) throws ParseException, RuntimeException,MethodNotSupportedException{
	Object ret=null;
	if (method_name.equals(s_breakpoint)){
	    if (m_StatementStepper!=null)
		m_StatementStepper.breakpoint(ctxt.getTopFrame());
	}else
	    ret=super.method(method_name,arguments,ctxt);
	return ret;
    }
    /**
     * Alerts the current statement stepper (if there is one) that an exception has occurred
     * @param ctxt the current ExecutableContext
     * @param e the exception to throw
     * @param desc the exception description without location information
     * @return true if the exception should be thrown, false if the statement stepper requests the exception is not thrown
     */
    public boolean checkThrowException(ExecutableContext ctxt,Exception e,String desc){
	boolean throw_exception=true;
	if (m_StatementStepper!=null)
	    throw_exception=m_StatementStepper.exceptionEncountered(ctxt.getTopFrame(),e,desc);
	return throw_exception;
    }
    /**
     * This method converts the given value into a boolean value
     * <p>If the object is an instance of Boolean, the boolean value is returned, otherwise toString() is called, and
     * compared with "true"
     * @return the boolean equivalent of the object
     */
    public static boolean boolValue(Object value){
	boolean ret=false;
	if (value!=null){
	    if (value instanceof Boolean)
		ret=((Boolean)value).booleanValue();
	    else if (value.toString().equals(NodeTypes.s_True))
		ret=true;
	    if (value instanceof Character)
		ret=((Character)value).charValue()=='t';
	    else if (intValue(value)!=0)
		ret=true;
	}
	return ret;
    }
    /**
     * This method converts the given value into an integer value
     * <p>If the object is an instance of Integer, the integer value is returned, otherwise toFloat() is called, and
     * converted to an integer
     * @return the integer equivalent of the object
     */
    public static int intValue(Object value){
	int ret=0;
	if (value instanceof Integer)
	    ret=((Integer)value).intValue();
	if (value instanceof Boolean){
	    if (((Boolean)value).booleanValue())
		ret=1;
	}else if (value instanceof Character)
	    ret=(int)((Character)value).charValue();
        //#ifndef no_float
	else{
	    try{
		ret=(int)Math.floor(floatValue(value));
	    }catch(Exception e){
	    }
	}
        //#endif
	return ret;
    }
    /**
     * This method converts the given value into a double value
     * <p>If the object is an instance of Double, the integer value is returned. If the object is a boolean, true maps to 1.0f and false to 0.0f,  otherwise toString() is called, and converted to a double
     * @return the double equivalent of the object
     */
    //#ifndef no_float
    public static double floatValue(Object value){
	double ret=0.0;
	if (value instanceof Double)
	    ret=((Double)value).doubleValue();
	else{
	    if (value instanceof Boolean){
		if (((Boolean)value).booleanValue())
		    ret=1.0;
	    }else if (value instanceof Character){
		ret=(int)((Character)value).charValue();
	    }else
		try{
		    ret=new Double(value.toString()).doubleValue();
		}catch(Exception e){
		}
	}
	return ret;
    }
    //#endif
    /** Returns null - this is not a collection */
    public Object getValueAt(Object index,String attrib_name,ExecutableContext ctxt) throws RuntimeException{
	return null;
    }
    /** Does nothing - this is not a collection */
    public void setValueAt(Object index,String attrib_name,Object value,ExecutableContext ctxt) throws RuntimeException{
    }
    /**
     * Returns the value of the "tracing" variable - e.g. in Simkin:
     * <p> tracing=Interpreter.tracing;
     */
    public Object getValue(String field_name,String attrib,ExecutableContext ctxt) throws RuntimeException,FieldNotSupportedException{
	Object o=null;
	boolean bRet=false;
	if (field_name.equals(s_Tracing))
	    o=new Boolean(m_Tracing);
	else
	    o=super.getValue(field_name,attrib,ctxt);
	return o;
    }
    /**
     * Called to execute a statement
     * @return a return object
     */
    Object executeStat(StackFrame frame,StatNode stat) throws ParseException, RuntimeException{
        //#ifndef midp
	if (g_TraceExecution) trace(Utils.addStrings("executeStat ",NodeTypes.g_NodeNames[stat.getType()]));
        //#endif
	Object ret=null;
	// nb javacc line numbers start from 1, ours from zero
	frame.setLineNum(stat.getLineNumber()-1);
	int stat_type=stat.getType();
	boolean stop=false;
	if (m_StatementStepper!=null)
	    stop=(m_StatementStepper.statementExecuted(frame,stat_type)==false);
	if (stop==false){
	    switch(stat_type){
	    case NodeTypes.s_StatList:
		ret=executeStats(frame,(StatListNode)stat);
		break;
	    case NodeTypes.s_Method:
		evalMethod(frame,((MethodNode)stat).m_Ids);
		break;
	    case NodeTypes.s_if:
		ret=executeIfStat(frame,(IfNode)stat);
		break;
	    case NodeTypes.s_For:
		ret=executeForStat(frame,(ForNode)stat);
		break;
	    case NodeTypes.s_ForEach:
		ret=executeForEachStat(frame,(ForEachNode)stat);
		break;
	    case NodeTypes.s_while:
		ret=executeWhileStat(frame,(WhileNode)stat);
		break;
	    case NodeTypes.s_Assign:
		executeAssignStat(frame,(AssignNode)stat);
		break;
	    case NodeTypes.s_return:
		ret=executeReturnStat(frame,(ReturnNode)stat);
		break;
	    case NodeTypes.s_switch:
		ret=executeSwitchStat(frame,(SwitchNode)stat);
		break;
	    }
	}else
	    ret=g_null;
	return ret;
    }
    /**
     * Called to execute a block of statements
     * @return a return object
     */
    Object executeStats(StackFrame frame,StatListNode n) throws ParseException, RuntimeException{
	if (g_TraceExecution) trace(Utils.addStrings("executeStats ",new Integer(n.m_Stats.size()).toString()));
	Object ret=null;
	boolean stop=false;
	if (stop==false){
	    for (int i=0;i<n.m_Stats.size();i++){
		StatNode stat=(StatNode)(n.m_Stats.elementAt(i));
		ret=executeStat(frame,stat);
		if (ret!=null)
		    break;
	    }
	}else
	    ret=g_null;
	if (m_StatementStepper!=null)
	    stop=(m_StatementStepper.compoundStatementExecuted(frame)==false);
	return ret;
    }
    /**
     * Adds a local variable to the current list of local variables
     */
    void addLocalVariable(Hashtable var,String name,Object value){
	if (g_TraceExecution) trace(Utils.addStrings("Adding local variable:" ,name));
	var.put(name,value);
    }
    /**
     * looks for the given global variable.
     * @param name - the name of the global variable
     * @return the object found, or null if the global variable is undefined
     */
    public  Object findGlobalVariable(String name){
	synchronized(m_GlobalVars) {
	    return m_GlobalVars.get(name);
	}
    }
    /**
     * Adds a global variable to the current list
     * @param name - the name of the global variable, if one already exists it is replaced
     * @param r - the object to be added to the list
     */
    public  void addGlobalVariable(String name,Object r){
	synchronized(m_GlobalVars) {
	    m_GlobalVars.put(name,r);
	}
    }
    /**
     * Removes a global variable from the current list
     * @param name - the name of the global variable to be removed
     */
    public void removeGlobalVariable(String name){
	synchronized(m_GlobalVars) {
	    m_GlobalVars.remove(name);
	}
    }
    /**
     * Evaluates a method and returns the result
     * @param obj the object owning the code
     * @param var the current local variable list
     * @param ids the id list
     * @return the value from the method call
     */
    Object evalMethod(StackFrame frame,IdListNode ids) throws ParseException, RuntimeException{
	Object ret=null;
	IdNode idNode=(IdNode)ids.m_Ids.elementAt(ids.m_Ids.size()-1);
	String method_name=idNode.m_Name;
	if (ids.m_Ids.size()==1){
	    ret=makeMethodCall(frame,frame.getObject(),method_name,idNode.m_ArrayIndex,ids.m_Attrib,idNode.m_Exprs);
	}else{
	    Object called_obj=followIdList(frame,ids);
	    ret=makeMethodCall(frame,called_obj,method_name,idNode.m_ArrayIndex,ids.m_Attrib,idNode.m_Exprs);
	}
	return ret;
    }
    /**
     * executes a return statement by copying the expression's value into the return variable
     * @return the result of the result expression
     */
    Object executeReturnStat(StackFrame frame,ReturnNode n) throws ParseException, RuntimeException{
        Object ret=g_null;
        if (n.m_Expr!=null)
            ret=evaluate(frame,n.m_Expr);
        return ret;
    }
    /**
     * executes a while statement by repeatedly executing the block of statements until the test expression is false
     * @return the return object
     */
    Object executeWhileStat(StackFrame frame,WhileNode n) throws ParseException, RuntimeException{
	Object ret=null;
	// loop until an enclosing statement is a return statement
	while(ret==null){
	    Object expr=evaluate(frame,n.m_Expr);
	    // or until the loop expression is false
	    if (boolValue(expr)){
		if (n.m_Stats!=null)
		    ret=executeStat(frame,n.m_Stats);
	    }else
		break;
	}
	return ret;
    }
    /**
     * executes a for each statement by obtaining an ExecutableIterator object and iterating over it
     * @return the return object
     */
    Object executeForEachStat(StackFrame frame,ForEachNode n) throws ParseException, RuntimeException{
	Object ret=null;
	String checked_id=checkIndirectId(frame,n.m_Id.m_Name);
	Object expr=evaluate(frame,n.m_Expr);
	if (expr instanceof Executable){
	    ExecutableIterator iterator=null;
	    if (n.m_Qualifier!=null)
		iterator=((Executable)expr).createIterator(n.m_Qualifier.m_Name);
	    else
		iterator=((Executable)expr).createIterator();
	    if (iterator!=null){
		Object value;
		while (ret==null){
		    value=iterator.next();
		    if (value==null)
			break;
		    else{
			addLocalVariable(frame.getVars(),checked_id,value);
			if (n.m_Stats!=null){
			    ret=executeStats(frame,n.m_Stats);
			}
		    }
		}
	    }else
		runtimeException(frame,Messages.CouldNotCreateIterator);
	}else
	    runtimeException(frame,Messages.CannotApplyForEach);
	return ret;
    }

    /**
     * executes a for statement by repeatedly executing the block of statements for the range given in the statement
     * @return the return object
     */
    Object executeForStat(StackFrame frame,ForNode n) throws ParseException, RuntimeException{
	Object ret=null;
	String checked_id=checkIndirectId(frame,n.m_Id.m_Name);
	Object start_expr=evaluate(frame,n.m_StartExpr);
	int start_value=intValue(start_expr);
	Object end_expr=evaluate(frame,n.m_EndExpr);
	int end_value=intValue(end_expr);
	int step_value=1;
	if (n.m_StepExpr!=null){
	    Object step_expr=evaluate(frame,n.m_StepExpr);
	    step_value=intValue(step_expr);
	}
	if (end_value!=start_value){
	    if (step_value>0){
		if (end_value>start_value){
		    for (int i=start_value;i<end_value;i+=step_value){
			addLocalVariable(frame.getVars(),checked_id,new Integer(i));
			if (n.m_Stats!=null){
			    ret=executeStats(frame,n.m_Stats);
			    if (ret!=null)
				break;
			}
		    }
		}else
		    runtimeException(frame,Messages.EndNotGreaterThanStart);
	    }else{
		if (step_value<0){
		    if (start_value>end_value){
			for (int i=start_value;i>end_value;i+=step_value){
			    addLocalVariable(frame.getVars(),checked_id,new Integer(i));
			    if (n.m_Stats!=null){
				ret=executeStats(frame,n.m_Stats);
				if (ret!=null)
				    break;
			    }
			}
		    }else
			runtimeException(frame,Messages.StartNotGreaterThanEnd);
		}else
		    runtimeException(frame,Messages.ZeroStep);
	    }
	}// else - do nothing
	return ret;
    }
    /**
     * executes a switch statement by evaluating an expression and testing it for equality against the case statements
     * @return the return object
     */
    Object executeSwitchStat(StackFrame frame,SwitchNode n) throws ParseException, RuntimeException{
	Object ret=null;
	Object expr=evaluate(frame,n.m_Expr);
	CaseNode caseFound=null;
	for (int i=0;i<n.m_Cases.m_Cases.size();i++){
	    if (g_TraceExecution) trace(Utils.addStrings("checking case ",new Integer(i).toString()));
	    CaseNode caseNode=(CaseNode)(n.m_Cases.m_Cases.elementAt(i));
	    Object testExpr=evaluate(frame,caseNode.m_Expr);
	    if ((expr==null && testExpr==null) || (expr!=null && expr.equals(testExpr))){
		caseFound=caseNode;
		break;
	    }
	}
	if (caseFound!=null){
	    ret=executeStats(frame,caseFound.m_Stats);
	}else if (n.m_DefaultCase!=null)
	    ret=executeStats(frame,n.m_DefaultCase.m_Stats);
	return ret;
    }
    /**
     * Executes an if..else statement
     * @return the return object
     */
    Object executeIfStat(StackFrame frame,IfNode n) throws ParseException, RuntimeException{
	Object ret=null;
	Object rExpr=evaluate(frame,n.m_Expr);
	if (boolValue(rExpr)){
	    if (n.m_Stats!=null)
		ret=executeStat(frame,n.m_Stats);
	}else
	    if (n.m_Else!=null)		
		ret=executeStat(frame,n.m_Else);
	return ret;
    }
    /**
     * Evaluates a series of argument expressions for a method call
     * @param obj - the object owning the script
     * @param var - the current local variables
     * @param n - the node containing the list of expressions
     * @param args - the array of arguments to be filled up
     */
    void evalArgs(StackFrame frame,ExprListNode n,Object [] args) throws ParseException, RuntimeException{
	if (n!=null && n.m_Exprs!=null)
	    for (int i=0;i<n.m_Exprs.size();i++){
		ExprNode parg=(ExprNode)(n.m_Exprs.elementAt(i));
		args[i]=evaluate(frame,parg);
	    }
    }
    /**
     * Checks whether an identifier is prefixed by "@". If it is, the id is first evaluated and then a variable with the value's name returns as the new id
     * @return the name, once the indirection has been resolved
     */
    String checkIndirectId(StackFrame frame,String name) throws ParseException{
	if (name.length()>0 && name.charAt(0)=='@'){
	    name=name.substring(1,name.length());
	    Object new_name=findValue(frame,name,null,null);
	    if (new_name!=null){
		name=new_name.toString();
	    }
	}
	return name;
    }	
    /**
     * searches for a named value. Looking first for built-in names, then within the given local variable table, then within the given object, and finally within the global variables
     * @param obj the object whose fields will be searched
     * @param var the local variable list
     * @param name the name of the value being sought
     * @param attrib the name of the attribute being sought (might be null)
     * @return the object found
     */
    Object findValue(StackFrame frame,String name,ExprNode array_index,String attrib) throws ParseException{
	Object ret=null;
	name=checkIndirectId(frame,name);
	if (name.length()>0){
	    // the name is prefixed with "self"
	    if (name.equals(NodeTypes.s_null)){
		ret=g_null;
	    }else if (name.equals(NodeTypes.s_Self)){
		if (attrib!=null || array_index!=null){
		    if (array_index!=null)
			ret=extractArrayValue(frame,frame.getObject(),array_index,attrib);
		    else
			ret=extractValue(frame,frame.getObject(),null,attrib);
		}else
		    ret=frame.getObject();
		// the value is "true"
	    }else if (name.equals(NodeTypes.s_True))
		ret=g_True;
	    // the value is "false"
	    else if (name.equals(NodeTypes.s_False))
		ret=g_False;
	    else{
		// otherwise first check in the local variables
		ret=frame.getVars().get(name);
		if (ret==null){
		    // then in the instance data for the object
		    try{
			if (g_TraceExecution) trace(Utils.addStrings("looking for instance var:",name));
			if (frame.getObject()!=null){
			    if (array_index!=null)
				ret=extractFieldArrayValue(frame,frame.getObject(),name,array_index,attrib);
			    else
				ret=extractValue(frame,frame.getObject(),name,attrib);
			}else{
			    synchronized(m_GlobalVars) {
				ret=extractValue(frame,m_GlobalVars.get(name),null,attrib);
			    }
			    if (ret==null)
				runtimeException(frame,Messages.formatMessage(Messages.FieldNotFound,name));
			    else
				// handle the array or attribute case
				if (attrib!=null || array_index!=null){
				    if (array_index!=null)
					ret=extractArrayValue(frame,ret,array_index,attrib);
				    else
					ret=extractValue(frame,ret,null,attrib);
				}
			}
		    }catch(Exception e){
			if (g_TraceExecution) trace(Utils.addStrings("looking for global var:",name));
			// finally in the global variables list
			ret=extractValue(frame,m_GlobalVars.get(name),null,attrib);
			if (ret==null)
			    runtimeException(frame,Utils.addStrings(Messages.formatMessage(Messages.FieldNotFound,name)," (",e.toString(),")"),e);
			else{
			    // handle the array or attribute case
			    if (attrib!=null || array_index!=null){
				if (array_index!=null)
				    ret=extractArrayValue(frame,ret,array_index,attrib);
				else
				    ret=extractValue(frame,ret,null,attrib);
			    }
			}
		    }
		}else{
		    if (attrib!=null || array_index!=null){
			if (array_index!=null)
			    ret=extractArrayValue(frame,ret,array_index,attrib);
			else
			    ret=extractValue(frame,ret,null,attrib);
		    }
		}
	    }
	}	
	return ret;
    }
    /** this method dereferences an instance variable of an object, and then applies an array index to the result
     * @param obj the object owning the context of the current code
     * @param var the local variables for the current contaxt
     * @param called_obj the object owning the field to be dereferenced
     * @param field_name the name of the field
     * @param array_index an expression denoting the array index
     * @param attrib an optional attribute (cann be null)
     */
    Object extractFieldArrayValue(StackFrame frame,Object called_obj,String field_name,ExprNode array_index,String attrib) throws ParseException{
	if (g_TraceExecution) trace(Utils.addStrings("extractFieldArrayValue:",field_name));
	Object ret=extractValue(frame,called_obj,field_name,null);
	ret=extractArrayValue(frame,ret,array_index,attrib);
	return ret;
    }
    /**
     * This method extracts a value from an array object
     * @param obj - the owner of the current context
     * @param var - local variables
     * @param called_obj - the owner of the value
     * @param array_index - an expression indicating an array index 
     * @param attrib - the name of an attribute (can be null)
     */
    Object extractArrayValue(StackFrame frame,Object called_obj,ExprNode array_index,String attrib) throws ParseException{
	if (g_TraceExecution) trace("extractArrayValue:");
	Object ret=called_obj;
	Object array_object=evaluate(frame,array_index);
	if (ret instanceof Executable)
	    ret=((Executable)ret).getValueAt(array_object,attrib,frame.getContext());
	else if (ret.getClass().isArray()){
	    if (attrib==null){
		Object[] array=(Object[])ret;
		ret=array[intValue(array_object)];
	    }else
		runtimeException(frame,Messages.NoAttributesOnArrays);
	}else
	    runtimeException(frame,Messages.ArrayIndexNotOnExecutable);
	if (ret==null)
	    ret=g_null;
	return ret;
    }
    /**
     * This method extracts a value from an object
     * @param obj - the owner of the current context
     * @param var - local variables
     * @param called_obj - the owner of the value
     * @param name - the name of the field (can be null)
     * @param attrib - the name of an attribute (can be null)
     */
    Object extractValue(StackFrame frame,Object called_obj,String name,String attrib) throws ParseException{
	if (name!=null)
	    name=checkIndirectId(frame,name);
	if (g_TraceExecution) trace(Utils.addStrings("extractValue:",name," "+attrib));
	Object ret=called_obj;
	if (attrib!=null){
	    // if an attribute is passed, this only works with Executable-type objects
	    if (called_obj instanceof Executable){
		try{
		    ret=((Executable)called_obj).getValue(name,attrib,frame.getContext());
		}catch(Exception e){
		    runtimeException(frame,Utils.addStrings(Messages.formatMessage(Messages.AttributeNotFound,attrib)," (",e.toString(),")"));
		}
	    }else{
		runtimeException(frame,Messages.AttributeNotOnExecutable);
	    }
	}else{
	    if (name!=null){
		if (called_obj instanceof Executable){
		    try{
			ret=((Executable)called_obj).getValue(name,attrib,frame.getContext());
		    }catch(Exception e){
			throw new RuntimeException(e.getMessage());
			//runtimeException(frame,Messages.formatMessage(Messages.FieldNotFound,name)+" ("+e.toString()+")");
		    }
		}else{
		    try{
			if (m_ReflectionEnabled)
			    ret=reflectiveGetValue(frame.getContext(),called_obj,name);
			else
			    runtimeException(frame,Messages.formatMessage(Messages.FieldNotFound,name));
		    }catch(Exception e){
			//			trace(e);
			runtimeException(frame,Utils.addStrings(Messages.formatMessage(Messages.FieldNotFound,name)," (",e.toString(),")"));
		    }
		}
	    }
	}
	if (ret==null)
	    ret=g_null;
	return ret;
    }
    /**
     * this method assigns the value of an expression to a field in another object
     * @param obj the object in which context the method is executed
     * @param var the local variable list
     * @param n the portion of the parse tree for the assignment statement
     */
    void executeAssignStat(StackFrame frame,AssignNode n) throws ParseException, RuntimeException{
	Object value=evaluate(frame,n.m_Expr);
	IdNode idNode=(IdNode)(n.m_Ids.m_Ids.elementAt(n.m_Ids.m_Ids.size()-1));
	String name=checkIndirectId(frame,idNode.m_Name);
	if (n.m_Ids.m_Ids.size()==1){
	    if (name.equals(NodeTypes.s_Self)){
		try{
		    if (idNode.m_ArrayIndex!=null)
			insertArrayValue(frame,frame.getObject(),idNode.m_ArrayIndex,n.m_Attrib,value);
		    else
			insertValue(frame,frame.getObject(),null,n.m_Attrib,value);
		}catch(Exception e){
		    runtimeException(frame,Utils.addStrings(Messages.formatMessage(Messages.CouldNotSetAttributeOnClass,n.m_Attrib,frame.getObject().getClass().getName())," (",e.toString(),")"),e);
		}
	    }else{
		Object caller=frame.getObject();
		try{
		    if (idNode.m_ArrayIndex!=null){
			caller=findValue(frame,name,null,null);
			insertArrayValue(frame,caller,idNode.m_ArrayIndex,n.m_Attrib,value);
		    }else{
			if (n.m_Attrib!=null){
			    insertValue(frame,frame.getObject(),name,n.m_Attrib,value);
			}else{
			    insertValue(frame,frame.getObject(),name,n.m_Attrib,value);
			}
		    }
		}catch(FieldNotSupportedException e){
		    addLocalVariable(frame.getVars(),name,value);
		}catch(Exception e){
		    //		    trace(e);
		    runtimeException(frame,Utils.addStrings(Messages.formatMessage(Messages.CouldNotSetFieldOnClass,name,n.m_Attrib,caller.getClass().getName())," (",e.toString(),")"),e);
		}
	    }
	}else{
	    Object called_obj=followIdList(frame,n.m_Ids);
	    try{
		if(idNode.m_ArrayIndex!=null){
		    called_obj=extractValue(frame,called_obj,name,null);
		    insertArrayValue(frame,called_obj,idNode.m_ArrayIndex,n.m_Attrib,value);
		}else
		    insertValue(frame,called_obj,name,n.m_Attrib,value);
	    }catch(Exception e){
		//		trace(e);
		runtimeException(frame,Utils.addStrings(Messages.formatMessage(Messages.CouldNotSetFieldOnClass,name,n.m_Attrib,called_obj.getClass().getName())," (",e.toString(),")"),e);
	    }
	}
    }
    /**
     * This method sets a value into an object
     * @param obj - the object owning the current context
     * @param var - the current local variable list
     * @param called_obj - the object owning the value
     * @param name - the name of the field
     * @param attrib - the attribute name
     * @param value - the value to be set
     */
    void insertValue(StackFrame frame,Object called_obj,String name,String attrib,Object value) throws FieldNotSupportedException,ParseException{
	if (attrib!=null){
	    // this is the situation where the attribute is non-null
	    if (called_obj instanceof Executable){
		((Executable)called_obj).setValue(name,attrib,value,frame.getContext());
	    }else{
		runtimeException(frame,Messages.AttributeNotOnExecutable);
	    }
	}else{
	    if (called_obj instanceof Executable)
		((Executable)called_obj).setValue(name,attrib,value,frame.getContext());
	    else
		if (m_ReflectionEnabled)
		    reflectiveSetValue(frame.getContext(),called_obj,name,value);
		else
		    runtimeException(frame,Messages.formatMessage(Messages.CouldNotSetField,name));
	}
    }
    /**
     * This method sets a value into an array object
     * @param obj - the object owning the current context
     * @param var - the current local variable list
     * @param called_obj - the object owning the value
     * @param attrib - the attribute name
     * @param value - the value to be set
     */
    void insertArrayValue(StackFrame frame,Object called_obj,ExprNode array_index,String attrib,Object value) throws FieldNotSupportedException,ParseException{
	Object array_object=evaluate(frame,array_index);
	if (called_obj instanceof Executable)
	    ((Executable)called_obj).setValueAt(array_object,attrib,value,frame.getContext());
	else if (called_obj.getClass().isArray()){
	    if (attrib==null){
		Object[] array=(Object[])called_obj;
		array[intValue(array_object)]=value;
	    }else
		runtimeException(frame,Messages.AttributeNotOnExecutable);
	}else{
	    runtimeException(frame,Messages.ArrayIndexNotOnExecutable);
	}
    }
    /**
     * This method follows an id list to the penultimate id, attempting to
     * locate objects
     * @param obj - owner of this method
     * @param var - current local variables
     * @param idlist - list of id's
     * @return object - the object found by following the list
     */
    Object followIdList(StackFrame frame,IdListNode idList) throws ParseException{
	IdNode idNode=(IdNode)idList.m_Ids.elementAt(0);
	String name=checkIndirectId(frame,idNode.m_Name);
	Object curr_obj=null;
	if (idNode.m_Exprs==null)
	    curr_obj=findValue(frame,name,idNode.m_ArrayIndex,null);
	else
	    curr_obj=makeMethodCall(frame,frame.getObject(),name,idNode.m_ArrayIndex,null,idNode.m_Exprs);
	for (int i=1;i<idList.m_Ids.size()-1;i++){
	    idNode=(IdNode)idList.m_Ids.elementAt(i);
	    name=checkIndirectId(frame,idNode.m_Name);
	    if (idNode.m_Exprs==null){
		if (idNode.m_ArrayIndex!=null)
		    curr_obj=extractFieldArrayValue(frame,curr_obj,name,idNode.m_ArrayIndex,null);
		else
		    curr_obj=extractValue(frame,curr_obj,name,null);
	    }else
		curr_obj=makeMethodCall(frame,curr_obj,name,idNode.m_ArrayIndex,null,idNode.m_Exprs);
	}
	return curr_obj;
    }
    /**
     * This method calls a method on the given object
     * @param obj - the owner of the current context
     * @param var - the current local variables
     * @param called_obj - the object whose method is to be called
     * @param name - the name of the method
     * @param exprs - the expression list to be passed as parameters - these will be evaluated
     */
    Object makeMethodCall(StackFrame frame,Object called_obj,String name,ExprNode array_index,String attrib,ExprListNode exprs) throws ParseException{
	String method_name=checkIndirectId(frame,name);
	Object [] args=null;
	Object ret=null;
	if (exprs!=null && exprs.m_Exprs.size()>0)
	    args=new Object[exprs.m_Exprs.size()];
	evalArgs(frame,exprs,args);
	if (m_Tracing)  traceString(Utils.addStrings("Interpreter: ",called_obj.getClass().getName(),"::"+method_name,"()\n"));
	try{
	    if (called_obj instanceof Executable){
		ret=((Executable)called_obj).method(method_name,args,frame.getContext());	
	    }else{
		if (m_ReflectionEnabled)
		    ret=reflectiveMethodCall(frame.getContext(),called_obj,method_name,args);
		else
		    runtimeException(frame,Messages.formatMessage(Messages.CouldNotCallMethodClass,method_name,called_obj.getClass().getName()));
	    }
	    // check if there's an array index after this method - if so, dereference the object
	    if (array_index!=null){
		Object array_object=evaluate(frame,array_index);
		if (ret instanceof Executable)
		    ret=((Executable)ret).getValueAt(array_object,attrib,frame.getContext());
		else if (ret.getClass().isArray()){
		    Object[] array=(Object[])ret;
		    ret=array[intValue(array_object)];
		}else
		    runtimeException(frame,Messages.ArrayIndexNotOnExecutable);
	    }else
		if (attrib!=null){
		    // extract value here
		}
	}catch(MethodNotSupportedException e){
	    runtimeException(frame,Utils.addStrings(Messages.formatMessage(Messages.CouldNotCallMethodClass,method_name,called_obj.getClass().getName())," (",e.toString(),")"),e);
	}catch(Exception e){
	    //	    trace(e);
	    runtimeException(frame,e.toString(),e);
	}
	if (m_Tracing)
	    traceString(Utils.addStrings("Interpreter: ",method_name,"() returns ",(ret!=null?ret.toString():"null"),"\n"));
	if (ret==null)
	    ret=g_null;
	return ret;
    }
    /** 
     * This method evaluates an expression
     * @param obj the object in whose context the expression is evaluated
     * @param var the local variable list
     * @param n the portion of the parse tree for this expression
     * @return the value of the expression
     */
    Object evaluate(StackFrame frame,ExprNode n) throws ParseException, RuntimeException{
	Object ret=null;
	Object other=null;
        //#ifndef midp
	if (g_TraceExecution) trace(Utils.addStrings("evaluate ",NodeTypes.g_NodeNames[n.getType()]));
        //#endif
	switch(n.getType()){
	case NodeTypes.s_IdList:{
	    IdListNode fnode=(IdListNode)n;
	    IdNode idNode=(IdNode)fnode.m_Ids.elementAt(fnode.m_Ids.size()-1);
	    String name=idNode.m_Name;
            //#ifndef midp
	    if (g_TraceExecution) trace(Utils.addStrings("evaluate ",NodeTypes.g_NodeNames[n.getType()]," name \"",name,"\""));
            //#endif
	    if (fnode.m_Ids.size()==1){
		if (idNode.m_Exprs==null)
		    ret=findValue(frame,name,idNode.m_ArrayIndex,fnode.m_Attrib);
		else
		    ret=makeMethodCall(frame,frame.getObject(),name,idNode.m_ArrayIndex,fnode.m_Attrib,idNode.m_Exprs);
	    }else{
		name=checkIndirectId(frame,name);
		Object called_obj=followIdList(frame,fnode);
		if (idNode.m_Exprs==null){
		    if (idNode.m_ArrayIndex!=null)
			ret=extractFieldArrayValue(frame,called_obj,name,idNode.m_ArrayIndex,fnode.m_Attrib);
		    else
			ret=extractValue(frame,called_obj,name,fnode.m_Attrib);
		}else{
		    ret=makeMethodCall(frame,called_obj,name,idNode.m_ArrayIndex,fnode.m_Attrib,idNode.m_Exprs);
		}
	    }
	    break;
	}
	case NodeTypes.s_String:
	    ret=((LiteralNode)n).m_Value;
	    break;
	case NodeTypes.s_Character:
	    ret=new Character(((LiteralNode)n).m_Value.charAt(0));
	    break;
	case NodeTypes.s_Integer:
	    try{
		ret=new Integer(Integer.parseInt(((LiteralNode)n).m_Value));
	    }catch(Exception e){
		ret=g_Zero;
	    }
	    break;
            //#ifndef no_float
	case NodeTypes.s_Float:
	    ret=new Double(((LiteralNode)n).m_Value);
	    break;
            //#endif
	default:{
		OpNode o=(OpNode)n;
		Object item1=evaluate(frame,o.m_Expr1);
		switch(n.getType()){
		case NodeTypes.s_not:
		    if (boolValue(item1))
			ret=g_False;
		    else
			ret=g_True;
		    break;
		case NodeTypes.s_and:
		    if (boolValue(item1) && boolValue(evaluate(frame,o.m_Expr2)))
			ret=g_True;
		    else
			ret=g_False;
		    break;
		case NodeTypes.s_or:
		    if (boolValue(item1) || boolValue(evaluate(frame,o.m_Expr2)))
			ret=g_True;
		    else
			ret=g_False;
		    break;
		case NodeTypes.s_Equals:
		    other=evaluate(frame,o.m_Expr2);
		    ret=g_False;
		    if (other!=null){
			boolean equals=false;
			if (item1 instanceof String)
			    equals=((String)item1).equals(other.toString());
			else if (item1 instanceof Integer)
			    equals=((Integer)item1).intValue()==intValue(other);
                        //#ifndef no_float
			else if (item1 instanceof Double)
			    equals=((Double)item1).doubleValue()==floatValue(other);
                        //#endif
			else if (item1 instanceof Boolean)
			    equals=((Boolean)item1).booleanValue()==boolValue(other);
			else
			    equals=item1.equals(other);
			if (equals)
			    ret=g_True;
		    }
		    break;
		case NodeTypes.s_NotEquals:
		    other=evaluate(frame,o.m_Expr2);
		    if (other!=null && item1.equals(other))
			ret=g_False;
		    else
			ret=g_True;
		    break;
		case NodeTypes.s_Concat:{
		    String str1="";
		    if (item1!=null)
			str1=item1.toString();
		    other=evaluate(frame,o.m_Expr2);
		    String str2="";
		    if (other!=null)
			str2=other.toString();
		    ret=Utils.addStrings(str1,str2);
		    break;
		}
		case NodeTypes.s_Minus:
		    if (item1!=null){
                        //#ifndef no_float
			if (item1 instanceof Double)
			    ret=new Double(-((Double)item1).doubleValue());
			else
                            //#endif
			    ret=new Integer(-intValue(item1));
		    }else
			ret=null;
		    break;
		default:{
			Object item2=evaluate(frame,o.m_Expr2);
			if (item1!=null && item2!=null){
                            //#ifndef no_float
			    boolean useIntegerArith=(item1 instanceof Integer) && (item2 instanceof Integer);
			    if (useIntegerArith){
                                //#endif
				int val1=intValue(item1);
				int val2=intValue(item2);
				switch(n.getType()){
				case NodeTypes.s_More:
				    if (val1>val2)
					ret=g_True;
				    else
					ret=g_False;
				    break;
				case NodeTypes.s_MoreEqual:
				    if (val1>=val2)
					ret=g_True;
				    else
					ret=g_False;
				    break;
				case NodeTypes.s_Less:
				    if (val1<val2)
					ret=g_True;
				    else
					ret=g_False;
				    break;
				case NodeTypes.s_LessEqual:
				    if (val1<=val2)
					ret=g_True;
				    else
					ret=g_False;
				    break;
				case NodeTypes.s_Plus:
				    ret=new Integer(val1+val2);
				    break;
				case NodeTypes.s_Subtract:
				    ret=new Integer(val1-val2);
				    break;
				case NodeTypes.s_Divide:
				    if (val2==0){
					ret=new Integer(0);
					runtimeException(frame,Messages.DivideByZero);
				    }else
					ret=new Integer((int)((long)val1/(long)val2));
				    break;
				case NodeTypes.s_Mult:
				    ret=new Integer(val1*val2);break;
				case NodeTypes.s_Mod:
				    ret=new Integer(val1%val2);break;
				}
                                //#ifndef midp
				if (g_TraceExecution) trace(Utils.addStrings("evaluate value1=",new Integer(val1).toString()," value2=",new Integer(val2).toString()," result of ",NodeTypes.g_NodeNames[n.getType()],"=",ret.toString()));
                                //#endif
                            //#ifndef no_float
			    }else{
				double val1=floatValue(item1);
				double val2=floatValue(item2);
				switch(n.getType()){
				case NodeTypes.s_More:
				    if (val1>val2)
					ret=g_True;
				    else
					ret=g_False;
				    break;
				case NodeTypes.s_MoreEqual:
				    if (val1>=val2)
					ret=g_True;
				    else
					ret=g_False;
				    break;
				case NodeTypes.s_Less:
				    if (val1<val2)
					ret=g_True;
				    else
					ret=g_False;
				    break;
				case NodeTypes.s_LessEqual:
				    if (val1<=val2)
					ret=g_True;
				    else
					ret=g_False;
				    break;
				case NodeTypes.s_Plus:
				    ret=new Double(val1+val2);break;
				case NodeTypes.s_Subtract:
				    ret=new Double(val1-val2);break;
				case NodeTypes.s_Divide:
				    if (val2==0){
					ret=new Integer(0);
					runtimeException(frame,Messages.DivideByZero);
				    }else
					ret=new Double(val1/val2);
				    break;
				case NodeTypes.s_Mult:
				    ret=new Double(val1*val2);break;
				case NodeTypes.s_Mod:
				    ret=new Double(val1%val2);break;
				}
                                //#ifndef midp
				if (g_TraceExecution) trace(Utils.addStrings("evaluate value1=",new Double(val1).toString()," value2=",new Double(val2).toString()," result of ",NodeTypes.g_NodeNames[n.getType()],"=",ret.toString()));
                                //#endif
			    }
                                //#endif no_float
			}else
			    ret=null;
		    }
		}
	    }
	}
        //#ifndef no_float
	if (g_TraceExecution) trace(Utils.addStrings("result of ",NodeTypes.g_NodeNames[n.getType()],"=",((ret!=null)?ret.toString():"null")));
        //#endif
	return ret;
    }
    /**
     * This method parses a string full of Simkin code and returns 
     * the parse tree. If a parse error occurs, a ParseException is thrown
     * <p>For example the following string could be passed:
     * <p>
     <pre>(a,b){
     a=b+1;
     return a;
     }
     </pre>
     * @param location used to help identify the code in error messages
     * @param code a string containing the Simkin code
     * @param ctxt the owning ExecutableContext
     * @return a ParseNode representing the parse tree for the code
     */
    public ParseNode parse(String location,String code,ExecutableContext ctxt) throws ParseException{
	StringReader buffer=new StringReader(code);
	Parser parser = new Parser(buffer);
	ParseNode topNode=null;
	try{
	    topNode=parser.executable_code();
	}catch(ParseException e){
	    e.location=location;
	    if (checkThrowException(ctxt,e,e.getMessage()))
		throw e;
	    throw e;
	}catch(TokenMgrError e){
	    ParseException pe=new ParseException(Utils.addStrings(e.getMessage()," in ",location));
	    pe.location=location;
	    if (checkThrowException(ctxt,pe,e.getMessage()))
		throw pe;
	}
	return topNode;
    }
    /**
     * This method parses a string of Simkin statements, excluding parameters and enclosing braces.
     * The method returns a parse tree. If a parse error occurs, a ParseException is thrown
     * <p> For example the following string could be passed (note the absence of enclosing braces around the statement list):
     <pre>
     a=b+1;
     return a;
     </pre>
     * @param location used to help identify the code in error messages
     * @param paramNames a vector of names for the parameters
     * @param code a string containing the Simkin code
     * @param ctxt the owning ExecutableContext
     * @return a ParseNode representing the parse tree for the code
     */
    public ParseNode parseExternalParams(String location,Vector paramNames,String code,ExecutableContext ctxt) throws ParseException{
	StringReader reader=new StringReader(code);
	Parser parser = new Parser(reader);
	MethodDefNode methodNode=new MethodDefNode(0);
	for (int i=0;i<paramNames.size();i++)
	    methodNode.m_Params.addId(new IdNode(0,(String)paramNames.elementAt(i),null,null));
	try{
	    methodNode.m_Stats=parser.stat_list();
	}catch(ParseException e){
	    e.location=location;
	    if (checkThrowException(ctxt,e,e.getMessage()))
		throw e;
	}catch(TokenMgrError e){
	    ParseException pe=new ParseException(Utils.addStrings(e.getMessage()," in ",location));
	    pe.location=location;
	    if (checkThrowException(ctxt,pe,e.getMessage()))
		throw pe;
	}
	return methodNode;
    }
    /**
     * This method executes a piece of Simkin code within a string
     * <p>For example the following string could be passed:
     * <p>
     <pre>(a,b){
     a=b+1;
     return a;
     }
     </pre>
     * @param location a name used to identify the code in error messages
     * @param obj the object within whose context the code will execute
     * @param code a string containing some Simkin code
     * @param args and array of RValue arguments to the code
     * @param ctxt the owning ExecutableContext
     * @return returns an object containing the result of the method and the parse tree for the method, which can be safely cached
     * @throws ParseException this is thrown if there is a syntax error in the code
     * @throws RuntimeException this is thrown if there is an error during the execution of the code (such as method or field not found)
     */
    public ExecuteResult executeString(String location,Object obj,String code,Object [] args,ExecutableContext ctxt) throws RuntimeException, ParseException{
	ParseNode parseNode=parse(location,code,ctxt);
	Object ret=executeParseTree(location,obj,parseNode,args,ctxt);
	return new ExecuteResult(ret,parseNode);
    }
    /**
     * This method executes a piece of Simkin code within a string, it assumes that the parameters have been defined outside the statement block.
     * <p> For example the following string could be passed (note the absence of enclosing braces around the statement list):
     <pre>
     a=b+1;
     return a;
     </pre>
     * @param location a name used to identify the code in error messages
     * @param obj the object within whose context the code will execute
     * @param paramNames a vector of names for the parameters
     * @param code a string containing some Simkin code
     * @param args and array of RValue arguments to the code
     * @param ctxt the owning ExecutableContext
     * @return returns an object containing the result of the method and the parse tree for the method, which can be safely cached
     * @throws ParseException this is thrown if there is a syntax error in the code
     * @throws RuntimeException this is thrown if there is an error during the execution of the code (such as method or field not found)
     */
    public ExecuteResult executeStringExternalParams(String location,Object obj,Vector paramNames,String code,Object[] args,ExecutableContext ctxt) throws RuntimeException, ParseException{
	ParseNode parseNode=parseExternalParams(location,paramNames,code,ctxt);
	Object ret=executeParseTree(location,obj,parseNode,args,ctxt);
	return new ExecuteResult(ret,parseNode);
    }
    /**
     * This method executes a piece of Simkin code from a parse tree.<p>You can call this with a parse tree produced by {@link simkin.Interpreter#parse parse}, {@link simkin.Interpreter#parseExternalParams parseExternalParams},{@link simkin.Interpreter#executeString executeString} or {@link simkin.Interpreter#executeStringExternalParams executeStringExternalParams}. This can be useful for caching parse trees to improve performance.
     * @param location a name used to identify the code in error messages
     * @param obj the object within whose context the code will execute
     * @param parseNode a pre-parsed tree of Simkin code
     * @param args and array of RValue arguments to the code
     * @param ctxt the owning ExecutableContext
     * @returns the return value of the method call
     * @throws ParseException this is thrown if there is a syntax error in the code
     * @throws RuntimeException this is thrown if there is an error during the execution of the code (such as method or field not found)
     */
    public Object executeParseTree(String location,Object obj,ParseNode parseNode,Object[] args,ExecutableContext ctxt) throws RuntimeException, ParseException{
	Hashtable vars=new Hashtable();
	StackFrame frame=new StackFrame(location,obj,vars,ctxt);
	Object ret=null;
	try{
	    if (parseNode!=null && parseNode.getType()==NodeTypes.s_MethodDefn){
		MethodDefNode methodNode=(MethodDefNode)parseNode;
		// fix up parameters by putting them in the local variable table
		if (args!=null && args.length>0){
		    for (int i=0;i<methodNode.m_Params.m_Ids.size();i++){
			IdNode idNode=(IdNode)(methodNode.m_Params.m_Ids.elementAt(i));
			String pparam=idNode.m_Name;
			addLocalVariable(vars,pparam,args[i]);
		    }
		}
		ret=executeStats(frame,methodNode.m_Stats);
	    }
	}finally{
	    ctxt.popStackFrame();
	}
	return ret;
    }
    /**
     * this function parses and evaluates an expression within the given context
     * @param location - a textual description of the location of the expression (this appears in error messages)
     * @param obj - the executable object which "owns" the expression
     * @param vars - a table of local variables defined for the expression
     * @param expression - a single Simkin expression which must begin with "=" for example "=a+b"
     * @param context context object to receive errors
     * @return the result of the expression
     * @exception ParseException - if a syntax error is encountered
     * @exception RuntimeException - if an error occurs while the expression is evaluated
     */
    public Object evaluateExpression(String location,Object obj,String expression,Hashtable  vars,ExecutableContext ctxt) throws RuntimeException, ParseException{
	Object ret=null;
	ParseNode parseNode=parse(location,expression,ctxt);
	if (parseNode!=null && parseNode instanceof ExprNode){
	    StackFrame frame=new StackFrame(location,obj,vars,ctxt);
	    ret=evaluate(frame,(ExprNode)parseNode);
	}
	return ret;
    }
    /** 
     * this method is called when there is a runtime error and throws a RuntimeException
     * @param ctxt the owning ExecutableContext
     * @param buffer the text of the error
     */
    static public void runtimeError(ExecutableContext ctxt,String buffer) throws RuntimeException{
	RuntimeException e=new RuntimeException(buffer);
	if (ctxt.getInterpreter().checkThrowException(ctxt,e,buffer))
	    throw e;
    }
    /** 
     * this method is called when there is a runtime error and throws a RuntimeException, it includes the location in the exception
     * @param frame the stack frame the error happened within
     * @param buffer the text of the error
     */
    void runtimeException(StackFrame frame,String buffer) throws RuntimeException{
	if (frame!=null){
	    StringBuffer msg=new StringBuffer(frame.getLocation());
	    msg.append(": ");
	    msg.append(new Integer(frame.getLineNum()).toString());
	    msg.append(": ");
	    msg.append(buffer);
	    RuntimeException e=new RuntimeException(msg.toString());
	    if (checkThrowException(frame.getContext(),e,buffer))
		throw e;
	}else{
	    RuntimeException e=new RuntimeException(buffer);
	    if (checkThrowException(frame.getContext(),e,buffer))
		throw e;
	}
    }
    /** 
     * this method is called when there is a runtime error and throws a RuntimeException, it includes the location in the exception. This version will also output the exception to the trace output
     * @param frame the stack frame the error happened within
     * @param ctxt the owning ExecutableContext
     * @param e the exception that occurred
     */
    void runtimeException(StackFrame frame,String buffer,Exception e) throws RuntimeException{
	trace(e);
	if (frame!=null){
	    StringBuffer msg=new StringBuffer(frame.getLocation());
	    msg.append(": ");
	    msg.append(new Integer(frame.getLineNum()).toString());
	    msg.append(": ");
	    msg.append(buffer);
	    RuntimeException e1=new RuntimeException(msg.toString());
	    if (checkThrowException(frame.getContext(),e1,buffer))
		throw e1;
	}else{
	    RuntimeException e1=new RuntimeException(buffer);
	    if (checkThrowException(frame.getContext(),e1,buffer))
		throw e1;
	}
    }
    /**
     * this method is called when the interpreter wants to output a message to the tracing log
     * @param buffer the trace message
     */
    void traceString(String buffer){
	trace(buffer);
    }
    /*
     * Searches a string for escape sequences \n and \t and converts them
     * @param str the string containing the sequences to be converted
     */
    static String replaceEscapes(String str){
	str=replace(str,"\\n","\n");
	str=replace(str,"\\\"","\"");
	str=replace(str,"\\t","\t");
        /*
        // look for any unicode escapes
        int u_esc=str.indexOf("\\u");
        if (u_esc>=0){
	    StringBuffer buffer=new StringBuffer(str.length());
            int pos=0;
            int index=str.indexOf("\\u");
	    while (index>=0){
		buffer.append(str.substring(pos,index));
                if (index<=str.length()-6){
                    String char_code=str.substring(index+2,index+6);
                    int i_char_code=Integer.parseInt(char_code,16);
                    buffer.append((char)i_char_code);
                    pos=index+6;
                    index=str.indexOf("\\u",pos);
                }else{
                    RuntimeException e1=new RuntimeException("Unicode constant in string is too short: "+buffer);
                    throw e1;
                }
	    }
	    if (pos<str.length())
		buffer.append(str.substring(pos));
	    str=buffer.toString();
        }
        */
	return str;
    }
    /**
     * @param original - the string to be replaced
     * @param old_value - the value to be replaced
     * @param new_value - the  new value
     * @return the original string with the old value replaced by the new
     */
    static String replace(String original,String old_value,String new_value){
	String replaced=original;
	int index=original.indexOf(old_value);
	if (index>=0){
	    int pos=0;
	    StringBuffer buffer=new StringBuffer(original.length());
	    while (index>=0){
		buffer.append(original.substring(pos,index));
		buffer.append(new_value);
		pos=index+old_value.length();
		index=original.indexOf(old_value,pos);
	    }
	    if (pos<original.length())
		buffer.append(original.substring(pos));
	    replaced=buffer.toString();
	}
	return replaced;
    }
    /** this method outputs a message to the trace - either the global class Tracer, or the current Trace callback 
	@param s the message to be sent to the tracer
    */
    public void trace(String s){
	if (m_TraceCallback!=null)
	    m_TraceCallback.trace(s);
	else
	    Tracer.trace(s);
    }
    /** Sends the text of an exception to the tracer
     * @param e the exception to be sent to the tracer
     */
    public void trace(Exception e){
	if (m_TraceCallback!=null)
	    m_TraceCallback.trace(e);
	else
	    Tracer.trace(e);
    }
    /** this method sets an object to receive trace messages. Pass 0 to fall back to standard mechanism (using Tracer)
     * @param cb the new tracer to use for trace commands (or null to fall back to Tracer)
     */
    public void setTraceCallback(TraceCallback cb){
	m_TraceCallback=cb;
    }
    /** this method sets an object to be called each time a statement is executed. 
     * @param stepper the new statement stepper
     */
    public void setStatementStepper(StatementStepper stepper){
	m_StatementStepper=stepper;
    }
    /**
     * This function attempts to make a public method call on a Java object by using reflection
     * @param ctxt the owning ExecutableContext
     * @param owner - the object to call a method on
     * @param method_name - the name of the method to call
     * @param arguments - the method arguments - types Integer, Double, Boolean and Character are converted to their primitive types
     * @return the result of the method call
     * @throws RuntimeException - if the method could not be called
     * @throws MethodNotSupportedException - if the method could not be found
     */
    public Object reflectiveMethodCall(ExecutableContext ctxt,Object owner,String method_name,Object[] arguments) throws RuntimeException,MethodNotSupportedException{
	Object ret=null;
        //#ifndef no_reflect
	Class[] arg_types=null;
	Class[] arg_types_str=null;
	Class string_class="".getClass();
	if (arguments!=null && arguments.length>0){
	    arg_types=new Class[arguments.length];
	    arg_types_str=new Class[arguments.length];
	    for (int i=0;i<arguments.length;i++){
		if (arguments[i] instanceof Integer){
		    arg_types[i]=Integer.TYPE;
		    arg_types_str[i]=Integer.TYPE;
		}else if (arguments[i] instanceof Double){
		    arg_types[i]=Double.TYPE;
		    arg_types_str[i]=Double.TYPE;
		}else if (arguments[i] instanceof Boolean){
		    arg_types[i]=Boolean.TYPE;
		    arg_types_str[i]=Boolean.TYPE;
		}else{
		    arg_types[i]=arguments[i].getClass();
		    arg_types_str[i]=string_class;
		}
	    }
	}
	if (m_Tracing){
	    StringBuffer args_buffer=new StringBuffer();
	    if (arg_types!=null){
		for (int i=0;i<arg_types.length;i++){
		    args_buffer.append(arg_types[i].getName());
		    args_buffer.append(" ");
		}
	    }
	    trace(Utils.addStrings("reflectiveMethodCall ",method_name," on ",owner.getClass().getName()," Argument types:",args_buffer.toString()));
	}
	Method method=null;
	boolean call_with_strings=false;
	try{
	    method=owner.getClass().getMethod(method_name,arg_types);
	}catch(NoSuchMethodException e1){
	    try{
		method=owner.getClass().getMethod(method_name,arg_types_str);
		call_with_strings=true;
	    }catch(NoSuchMethodException e){
		MethodNotSupportedException e2=new MethodNotSupportedException(e.toString());
		if (checkThrowException(ctxt,e2,e.getMessage()))
		    throw e2;
	    }
	}
	catch(Exception e){
	    runtimeException(ctxt.getTopFrame(),Messages.formatMessage(Messages.formatMessage(Messages.CouldNotFindMethod,method_name)," (",e.toString(),")"),e);
	}
	if (method!=null){
	    try{
		if (call_with_strings==false){
		    ret=method.invoke(owner,arguments);
		}else{
		    Object [] str_args=new Object[arguments.length];
		    for (int i=0;i<arguments.length;i++){
			if (arg_types_str[i]==string_class)
			    str_args[i]=arguments[i].toString();
			else
			    str_args[i]=arguments[i];
		    }
		    ret=method.invoke(owner,str_args);
		}
		if (ret==null){
		    if (m_Tracing)
			trace("Return value is null, changing to Simkin.Null");
		    ret=g_null;
		}
	    }catch(Exception e){
		runtimeException(ctxt.getTopFrame(),Utils.addStrings(Messages.formatMessage(Messages.CouldNotCallMethod,method_name)," (",e.toString(),")"),e);
	    }
	}
	if (g_TraceExecution) trace(Utils.addStrings("reflectiveMethodCall ",method_name," returns ",(ret!=null?ret.toString():"null")));
        //#endif
	return ret;
    }
    /**
     * This function attempts to set a public Java field using reflection
     * @param ctxt the owning ExecutableContext
     * @param owner - the object owning the field
     * @param field_name - the name of the field
     * @param value - the value to assign to the field
     * @throws RuntimeException - if the field could not be set (e.g. if it was private)
     * @throws FieldNotSupportedException - if the field could not be found
     */
    public void reflectiveSetValue(ExecutableContext ctxt,Object owner,String field_name,Object value) throws RuntimeException,FieldNotSupportedException{
        //#ifndef no_reflect
	boolean bFound=false;
	Field field=null;
	Class field_class=null;
	try{
	    field=owner.getClass().getField(field_name);
	    field_class=field.getType();
	    bFound=true;	
	}catch(NoSuchFieldException e){
	    FieldNotSupportedException f=new FieldNotSupportedException(e.toString());
	    throw f;
	}catch(Exception e){
	    if (g_TraceExecution) trace(e);
	    runtimeException(ctxt.getTopFrame(),Utils.addStrings(Messages.formatMessage(Messages.CouldNotAccessField,field_name)," (",e.toString(),")"),e);
	}
	if (bFound){
	    try{
		field.set(owner,value);
	    }catch(Exception e){
		runtimeException(ctxt.getTopFrame(),Utils.addStrings(Messages.formatMessage(Messages.CouldNotSetField,field_name)," (",e.toString(),")"),e);
	    }
	}
        //#endif
    }
    /**
     * This function attempts to retrieve a public Java field using reflection
     * @param ctxt the owning ExecutableContext
     * @param owner - the object owning the field
     * @param field_name - the name of the field
     * @return the value of the field
     * @throws RuntimeException - if the field could not be retrieved (e.g. if it was private)
     * @throws FieldNotSupportedException - if the field could not be found
     */
    public Object reflectiveGetValue(ExecutableContext ctxt,Object owner,String field_name) throws RuntimeException,FieldNotSupportedException{
	Object fieldValue=null;
        //#ifndef no_reflect
	Field field=null;
	try{
	    Class owner_class=owner.getClass();
	    if (owner_class.isArray() && (field_name.equals("length") || field_name.equals("numChildren")))
		fieldValue=new Integer(((Object[])owner).length);
	    else
		field=owner_class.getField(field_name);
	}catch(NoSuchFieldException e){
	    FieldNotSupportedException f=new FieldNotSupportedException(e.toString());
	    throw f;
	}catch(Exception e){
	    runtimeException(ctxt.getTopFrame(),Utils.addStrings(Messages.formatMessage(Messages.CouldNotAccessField,field_name)," (",e.toString(),")"),e);
	}
	if (fieldValue==null && field!=null){
	    try{
		fieldValue=field.get(owner);
	    }catch(Exception e){
		runtimeException(ctxt.getTopFrame(),Utils.addStrings(Messages.formatMessage(Messages.CouldNotReadField,field_name)," (",e.toString(),")"),e);
	    }
	    if (fieldValue==null)
		fieldValue=g_null;
	}
	if (g_TraceExecution) trace(Utils.addStrings("reflectiveGetValue: on ",owner.getClass().getName()," field name: ",field_name," returns: ",fieldValue.getClass().getName()));
        //#endif
	return fieldValue;
    }
    /** 
     * This method returns the current global variables for this instance of the Interpreter
     * @return a hashtable containing the current global variables
     */
    public Hashtable getGlobalVariables(){
	return m_GlobalVars;
    }
};
